{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 哈希表的基本操作\n",
    "![](https://kingname-1257411235.cos.ap-chengdu.myqcloud.com/2019-03-03-16-52-34.png)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 哈希表（Hash）\n",
    "\n",
    "### 基本功能\n",
    "\n",
    "Redis的Hash被设计出来，就是为了存储大量的键值对映射。储存相同数量的键值映射，Hash所占用的内存空间，远远小于字符串。\n",
    "\n",
    "### 基本语法\n",
    "\n",
    "```python\n",
    "import redis\n",
    "\n",
    "client = redis.Redis()\n",
    "\n",
    "# 向Hash表中添加一个键值对\n",
    "client.hset('Key', '字段名', '值')\n",
    "\n",
    "# 向Hash表中添加多个键值对\n",
    "client.hmset('Key', {'字段名1': '值1', '字段名2': '值2', '字段名3': '值3'})\n",
    "\n",
    "# 查询字段是否在哈希表中\n",
    "client.hexists('Key', '字段名')\n",
    "\n",
    "# 查询哈希表中一个有多少个字段\n",
    "client.hlen('Key')\n",
    "\n",
    "# 获取Hash表里面所有的字段名（慎用）\n",
    "client.hkeys('Key')\n",
    "\n",
    "# 读取一个字段中的数据\n",
    "client.hget('Key', '字段名')\n",
    "\n",
    "# 读取多个字段中的数据\n",
    "client.hmget('Key', ['字段名1', '字段名2', '字段名3'])\n",
    "\n",
    "# 读取全部字段（慎用）\n",
    "client.hgetall('Key')\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 向哈希表中写入数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 初始化Redis连接\n",
    "import redis\n",
    "import json\n",
    "client = redis.Redis()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 添加一条数据\n",
    "\n",
    "info = json.dumps({'name': '张小二', 'age': 18, 'salary': 100, 'address': '北京'})\n",
    "client.hset('user', 10001, info)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 插入多条数据\n",
    "info_dict = {\n",
    "    10002: json.dumps({'name': '王小三', 'age': 27, 'salary': 10000, 'address': '浙江'}),\n",
    "    10003: json.dumps({'name': '藏小四', 'age': 16, 'salary': 10, 'address': '四川'}),\n",
    "    10004: json.dumps({'name': '刘小五', 'age': 30, 'salary': 990, 'address': '武汉'})\n",
    "}\n",
    "client.hmset('user', info_dict)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 字段名不一定非要是数字，也可以是字母或者中文，字段值的数据类型也可以任意设定\n",
    "\n",
    "client.hset('user', '马小七', 780)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 检查字段信息"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 检查字段是否在Hash表中\n",
    "client.hexists('user', 10003)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "client.hexists('user', '马小七')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "client.hexists('user', '不存在的字段')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "client.hexists('不存在的Key', 10003)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "5"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 查询Hash中有多少字段\n",
    "client.hlen('user')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "马小七\n",
      "10002\n",
      "10003\n",
      "10001\n",
      "10004\n"
     ]
    }
   ],
   "source": [
    "keys = client.hkeys('user')\n",
    "for key in keys:\n",
    "    print(key.decode())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 获取键值对\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "返回的数据类型为：<class 'bytes'>, 数据内容为：b'780'\n",
      "数据解析以后为：780\n"
     ]
    }
   ],
   "source": [
    "# 获取单条数据\n",
    "data = client.hget('user', '马小七')\n",
    "print(f'返回的数据类型为：{type(data)}, 数据内容为：{data}')\n",
    "print(f'数据解析以后为：{data.decode()}')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "用JSON解析以后：{'name': '藏小四', 'age': 16, 'salary': 10, 'address': '四川'}\n"
     ]
    }
   ],
   "source": [
    "data = client.hget('user', 10003)\n",
    "data_dict = json.loads(data)\n",
    "print(f'用JSON解析以后：{data_dict}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'name': '张小二', 'age': 18, 'salary': 100, 'address': '北京'}\n",
      "{'name': '藏小四', 'age': 16, 'salary': 10, 'address': '四川'}\n"
     ]
    }
   ],
   "source": [
    "# 批量获取数据\n",
    "\n",
    "data_list = client.hmget('user', [10001, 10003])\n",
    "for data in data_list:\n",
    "    print(json.loads(data))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "先来看看返回的数据是什么样的：{b'\\xe9\\xa9\\xac\\xe5\\xb0\\x8f\\xe4\\xb8\\x83': b'780', b'10002': b'{\"name\": \"\\\\u738b\\\\u5c0f\\\\u4e09\", \"age\": 27, \"salary\": 10000, \"address\": \"\\\\u6d59\\\\u6c5f\"}', b'10003': b'{\"name\": \"\\\\u85cf\\\\u5c0f\\\\u56db\", \"age\": 16, \"salary\": 10, \"address\": \"\\\\u56db\\\\u5ddd\"}', b'10001': b'{\"name\": \"\\\\u5f20\\\\u5c0f\\\\u4e8c\", \"age\": 18, \"salary\": 100, \"address\": \"\\\\u5317\\\\u4eac\"}', b'10004': b'{\"name\": \"\\\\u5218\\\\u5c0f\\\\u4e94\", \"age\": 30, \"salary\": 990, \"address\": \"\\\\u6b66\\\\u6c49\"}'}\n"
     ]
    }
   ],
   "source": [
    "## 获取全部数据\n",
    "\n",
    "all_data = client.hgetall('user')\n",
    "print(f'先来看看返回的数据是什么样的：{all_data}')\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "字段名：马小七, 值：780\n",
      "字段名：10002, 值：{'name': '王小三', 'age': 27, 'salary': 10000, 'address': '浙江'}\n",
      "字段名：10003, 值：{'name': '藏小四', 'age': 16, 'salary': 10, 'address': '四川'}\n",
      "字段名：10001, 值：{'name': '张小二', 'age': 18, 'salary': 100, 'address': '北京'}\n",
      "字段名：10004, 值：{'name': '刘小五', 'age': 30, 'salary': 990, 'address': '武汉'}\n"
     ]
    }
   ],
   "source": [
    "for field, value in all_data.items():\n",
    "    print(f'字段名：{field.decode()}, 值：{json.loads(value)}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![读者交流QQ群](https://kingname-1257411235.cos.ap-chengdu.myqcloud.com/2019-02-16-09-59-56.png)\n",
    "![微信公众号](https://kingname-1257411235.cos.ap-chengdu.myqcloud.com/wechatplatform.jpg)\n",
    "![](https://kingname-1257411235.cos.ap-chengdu.myqcloud.com/2019-03-03-20-47-47.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 使用列表和Hash实现简单的任务队列\n",
    "![](https://kingname-1257411235.cos.ap-chengdu.myqcloud.com/2019-03-03-16-52-34.png)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 为什么需要任务队列？\n",
    "\n",
    "有一系列比较耗时的任务，不能实时完成，此时就需要使用任务队列排队完成。例如，网站注册时需要发送验证邮件。发送1封邮件需要2秒钟。现在只有一台邮件服务器，有100人同时注册。\n",
    "\n",
    "发邮件的过程不能让网站来完成。网站只是创建发邮件的任务，并把任务扔进任务队列中。另一个专门负责发邮件的程序从任务队列中读取任务，然后执行具体的发送操作。\n",
    "\n",
    "### 任务队列需要实现哪些功能？\n",
    "\n",
    "1. 添加任务\n",
    "2. 删除任务\n",
    "3. 暂停任务\n",
    "4. 恢复被暂停的任务并重新排队\n",
    "\n",
    "### 如何设计邮件的数据结构？\n",
    "\n",
    "* 列表中存放任务ID\n",
    "* Hash中存放任务详情，字段名为任务ID\n",
    "\n",
    "任务详情的结构为：\n",
    "\n",
    "```python\n",
    "{\n",
    "    \"task_id\": 123,\n",
    "    \"target\": \"xxx@163.com\",\n",
    "    \"created_time\": \"2019-03-24 11:12:34\"\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 添加任务\n",
    "\n",
    "* 哈希表的Key为：task:detail\n",
    "* 列表的Key为：task:queue\n",
    "\n",
    "1. 首先创建任务详情，并写入Hash表中\n",
    "2. 把任务ID写入到列表中"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 初始化Redis连接\n",
    "\n",
    "import redis\n",
    "client = redis.Redis()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 添加任务\n",
    "import datetime\n",
    "import json\n",
    "\n",
    "\n",
    "def add_task(task_id, target):\n",
    "    task_detail = {'task_id': task_id,\n",
    "                   'target': target,\n",
    "                   'created_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n",
    "    client.hset('task:detail', task_id, json.dumps(task_detail))\n",
    "    client.rpush('task:queue', task_id)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 发邮件的程序读取任务\n",
    "\n",
    "def read_task():\n",
    "    task_id = client.blpop('task:queue')[1].decode()\n",
    "    task_detail = client.hget('task:detail', task_id)\n",
    "    target = json.loads(task_detail)['target']\n",
    "    print(f'给：{target} 发送邮件')\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 删除任务\n",
    "\n",
    "def del_task(task_id):\n",
    "    client.lrem('task:queue', 0, task_id)\n",
    "    client.hdel('task:detail', task_id)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 暂停和恢复任务\n",
    "\n",
    "暂停和恢复任务都不会影响task:detail，只需要控制task:queue中的task_id即可。\n",
    "\n",
    "### 暂停任务\n",
    "\n",
    "1. 把task_id从task:queue中移除\n",
    "2. 把task_id放入暂停列表：task:pause中\n",
    "\n",
    "### 恢复任务\n",
    "\n",
    "1. 把task_id从task:pause中移除\n",
    "2. 把task_id重新放入task:queue右侧"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 暂停任务\n",
    "\n",
    "def pause_task(task_id):\n",
    "    client.lrem('task:queue', 0, task_id)\n",
    "    client.rpush('task:pause', task_id)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 恢复任务\n",
    "\n",
    "def resume_task(task_id):\n",
    "    client.lrem('task:pause', 0, task_id)\n",
    "    client.rpush('task:queue', task_id)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 来测试一下我们的简易任务队列"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 添加任务\n",
    "add_task(1, 'contact@kingname.info')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "add_task(2, 'register@163.com')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "给：contact@kingname.info 发送邮件\n"
     ]
    }
   ],
   "source": [
    "# 读取任务\n",
    "read_task()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "add_task(3, 'rain@gmail.com')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "add_task(4, 'world@hotmail.com')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "## 删除任务\n",
    "del_task(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "add_task(5, 'hello@facebook.com')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "pause_task(4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "给：rain@gmail.com 发送邮件\n"
     ]
    }
   ],
   "source": [
    "read_task()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "给：hello@facebook.com 发送邮件\n"
     ]
    }
   ],
   "source": [
    "read_task()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 恢复任务\n",
    "resume_task(4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "给：world@hotmail.com 发送邮件\n"
     ]
    }
   ],
   "source": [
    "read_task()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![读者交流QQ群](https://kingname-1257411235.cos.ap-chengdu.myqcloud.com/2019-02-16-09-59-56.png)\n",
    "![微信公众号](https://kingname-1257411235.cos.ap-chengdu.myqcloud.com/wechatplatform.jpg)\n",
    "![](https://kingname-1257411235.cos.ap-chengdu.myqcloud.com/2019-03-03-20-47-47.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 有序集合\n",
    "![](https://kingname-1257411235.cos.ap-chengdu.myqcloud.com/2019-03-03-16-52-34.png)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 有序集合（Sorted Set）\n",
    "\n",
    "有序集合本质是`集合`，但是每一个元素都带有一个分数（Score），通过这个分数可以对元素进行排序。有序集合里面的元素不能重复，但分数可以重复。\n",
    "\n",
    "### 常用命令\n",
    "\n",
    "#### 添加数据\n",
    "\n",
    "**注意！！最新版本的redis-py修改了参数的类型，请读者以视频为准。书上的写法可能会导致报错**\n",
    "\n",
    "```python\n",
    "client.zadd('有序集合Key', {'值1': 分数1, '值2': 分数2, '值3': 分数3})\n",
    "```\n",
    "\n",
    "#### 修改元素的评分\n",
    "\n",
    "**redis-py也修改了这个方法的参数，请以视频为准，书上的写法在最新版的redis-py中会报错**\n",
    "```python\n",
    "client.zincrby('有序集合Key', 改变量, 集合中的元素)\n",
    "```\n",
    "\n",
    "#### 基于评分范围排序\n",
    "\n",
    "```python\n",
    "\n",
    "# 升序，注意第二个参数是下限，第三个参数是上限\n",
    "client.zrangebyscore('有序集合Key', 评分下限, 评分上限, 切片起始位置, 切片数量, withscores=False)\n",
    "\n",
    "# 降序，注意第二个参数是上限，第三个参数是下限\n",
    "client.zrevrangebyscore('有序集合Key', 评分上限, 评分下限, 切片起始位置, 切片数量, withscores=False)\n",
    "```\n",
    "\n",
    "#### 基于位置排序\n",
    "\n",
    "```python\n",
    "client.zrange('有序集合Key', 开始位置（含）, 结束位置（含）, desc=False, withscores=False)\n",
    "client.zrevrange('有序集合Key', 开始位置（含）, 结束位置（含）, withscores=False)\n",
    "```\n",
    "\n",
    "#### 查询值的排名\n",
    "\n",
    "```python\n",
    "client.zrank('有序集合Key', '值')\n",
    "client.zrevrank('有序集合Key', '值')\n",
    "```\n",
    "\n",
    "#### 查询值的评分\n",
    "\n",
    "```python\n",
    "client.zscore('有序集合Key', '值')\n",
    "```\n",
    "\n",
    "#### 弹出最大最小值（Redis 5.0新特性）\n",
    "\n",
    "```python\n",
    "client.zpopmax('有序集合Key')\n",
    "client.zpopmin('有序集合Key')\n",
    "```\n",
    "\n",
    "#### 统计元素个数\n",
    "\n",
    "```python\n",
    "# 统计所有元素的个数\n",
    "client.zcard('有序集合Key')\n",
    "\n",
    "# 统计某个评分分为内的元素个数\n",
    "client.zcount('有序集合Key', 评分上限, 评分下限)\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 初始化连接\n",
    "import redis\n",
    "\n",
    "client = redis.Redis()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 添加数据\n",
    "\n",
    "## 特别说明：redis-py库的参数格式做了修改，书上的写法使用最新的redis-py库会导致报错。请以本视频的写法为准。\n",
    "\n",
    "详情见：https://github.com/andymccurdy/redis-py#mset-msetnx-and-zadd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 添加一条数据\n",
    "\n",
    "client.zadd('gamerank', {'kingname': 10})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 同时添加多条数据\n",
    "\n",
    "client.zadd('gamerank', {'xiaoming': 8, 'Alice': 12, 'Ted': 7})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 修改评分\n",
    "\n",
    "## 注意：redis-py也修改了这个方法的参数，请以视频为准，书上的写法在最新版的redis-py中会报错\n",
    "\n",
    "详情见：https://github.com/andymccurdy/redis-py#zincrby"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "18.0"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 把评分增加8\n",
    "\n",
    "client.zincrby('gamerank', 8.0, 'kingname')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "11.5"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 把评分减0.5\n",
    "\n",
    "client.zincrby('gamerank', -0.5, 'Alice')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 基于评分范围排序"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "4"
      ]
     },
     "execution_count": 39,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 补充一些数据进去\n",
    "\n",
    "client.zadd('gamerank', {'Cine': 13, 'Jhon': 16, 'Susan': 15, 'Maxwell': 100})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[b'Alice', b'Cine', b'Susan']"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 升序排序，取前三个，不带分数\n",
    "\n",
    "client.zrangebyscore('gamerank', 10, 20, 0, 3, withscores=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(b'Susan', 15.0), (b'Jhon', 16.0), (b'kingname', 18.0)]"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 升序排序，取前三个，带分数\n",
    "\n",
    "client.zrangebyscore('gamerank', 15, 100, 0, 3, withscores=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(b'Maxwell', 100.0), (b'kingname', 18.0), (b'Jhon', 16.0)]"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 降序排序，取前三个，带分数\n",
    "\n",
    "# 特别注意，这里的上限在前，下限在后\n",
    "client.zrevrangebyscore('gamerank', 100, 15, 0, 3, withscores=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 基于位置排序"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[b'Ted',\n",
       " b'xiaoming',\n",
       " b'Alice',\n",
       " b'Cine',\n",
       " b'Susan',\n",
       " b'Jhon',\n",
       " b'kingname',\n",
       " b'Maxwell']"
      ]
     },
     "execution_count": 43,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 升序排序\n",
    "client.zrange('gamerank', 0, -1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(b'Ted', 7.0),\n",
       " (b'xiaoming', 8.0),\n",
       " (b'Alice', 11.5),\n",
       " (b'Cine', 13.0),\n",
       " (b'Susan', 15.0),\n",
       " (b'Jhon', 16.0),\n",
       " (b'kingname', 18.0),\n",
       " (b'Maxwell', 100.0)]"
      ]
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 升序排序，带分数\n",
    "\n",
    "client.zrange('gamerank', 0, -1, withscores=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(b'Maxwell', 100.0),\n",
       " (b'kingname', 18.0),\n",
       " (b'Jhon', 16.0),\n",
       " (b'Susan', 15.0),\n",
       " (b'Cine', 13.0),\n",
       " (b'Alice', 11.5),\n",
       " (b'xiaoming', 8.0),\n",
       " (b'Ted', 7.0)]"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 用升序排序的命令来实现降序排序\n",
    "\n",
    "client.zrange('gamerank', 0, -1, desc=True, withscores=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(b'Maxwell', 100.0), (b'kingname', 18.0), (b'Jhon', 16.0)]"
      ]
     },
     "execution_count": 46,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 用升序排序的命令来实现降序排序，但只取前3个\n",
    "\n",
    "client.zrange('gamerank', 0, 2, desc=True, withscores=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(b'Jhon', 16.0), (b'kingname', 18.0), (b'Maxwell', 100.0)]"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 升序排序，取最后3个数（也就是最大的三个数）\n",
    "client.zrange('gamerank', -3, -1, withscores=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[b'Maxwell',\n",
       " b'kingname',\n",
       " b'Jhon',\n",
       " b'Susan',\n",
       " b'Cine',\n",
       " b'Alice',\n",
       " b'xiaoming',\n",
       " b'Ted']"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 降序排序\n",
    "\n",
    "client.zrevrange('gamerank', 0, -1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(b'Maxwell', 100.0),\n",
       " (b'kingname', 18.0),\n",
       " (b'Jhon', 16.0),\n",
       " (b'Susan', 15.0),\n",
       " (b'Cine', 13.0),\n",
       " (b'Alice', 11.5),\n",
       " (b'xiaoming', 8.0),\n",
       " (b'Ted', 7.0)]"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 降序排序，带分数\n",
    "client.zrevrange('gamerank', 0, -1, withscores=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(b'Maxwell', 100.0), (b'kingname', 18.0), (b'Jhon', 16.0)]"
      ]
     },
     "execution_count": 50,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 降序排序，取前三个\n",
    "client.zrevrange('gamerank', 0, 2, withscores=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 查询值的排名"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "6"
      ]
     },
     "execution_count": 51,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 查询升序排名(注意，第一名对应的序号为0)\n",
    "client.zrank('gamerank', 'kingname')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 查询降序排名（注意，第一名对应的序号为0）\n",
    "client.zrevrank('gamerank', 'kingname')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 查询值的评分\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "18.0"
      ]
     },
     "execution_count": 53,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "client.zscore('gamerank', 'kingname')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 弹出最值"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(b'Maxwell', 100.0)]"
      ]
     },
     "execution_count": 54,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 弹出最大值\n",
    "\n",
    "client.zpopmax('gamerank')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(b'Ted', 7.0)]"
      ]
     },
     "execution_count": 55,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 弹出最小值\n",
    "\n",
    "client.zpopmin('gamerank')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 统计元素格式\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "6"
      ]
     },
     "execution_count": 56,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 统计所有元素的个数\n",
    "\n",
    "client.zcard('gamerank')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3"
      ]
     },
     "execution_count": 57,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 统计分数范围内的元素个数\n",
    "client.zcount('gamerank', 15, 20)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![读者交流QQ群](https://kingname-1257411235.cos.ap-chengdu.myqcloud.com/2019-02-16-09-59-56.png)\n",
    "![微信公众号](https://kingname-1257411235.cos.ap-chengdu.myqcloud.com/wechatplatform.jpg)\n",
    "![](https://kingname-1257411235.cos.ap-chengdu.myqcloud.com/2019-03-03-20-47-47.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 使用有序集合实现优先级队列和定时队列\n",
    "\n",
    "![](https://kingname-1257411235.cos.ap-chengdu.myqcloud.com/2019-03-03-16-52-34.png)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 优先级队列\n",
    "\n",
    "玩过网游的读者可能会有这样的经验，有一些网游在开服前几天特别火爆，此时服务器压力太大，必需要排队进入游戏。如果玩家没有氪金，那么可能要排好几个小时才能进入游戏，但如果充值了会员，可能只需要几分钟就能进入游戏。\n",
    "\n",
    "这就是一个优先级队列的应用。普通玩家的优先级低，VIP玩家优先级高，新的VIP玩家上线时能够直接插队到普通玩家的前面。可能有一些玩家一开始没有氪金，看到要排几个小时，于是充值了会员。此时优先级队列会自动修改玩家的优先级，把它排到前面去。\n",
    "\n",
    "使用Redis的有序集合就能够实现一个简单的优先级队列。在有序集合里面，玩家的ID就是有序集合的值，玩家的优先级就是元素的评分`score`。使用`zpopmax`就可以每一次弹出评分最高的玩家ID。通过`zincrby`就可以修改玩家的评分，从而改动优先级。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 108,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3"
      ]
     },
     "execution_count": 108,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 添加初始数据\n",
    "\n",
    "client = redis.Redis()\n",
    "\n",
    "vip = {\n",
    "    'kingname': 1000,\n",
    "    'xiaoming': 800,\n",
    "    'Alice':800\n",
    "}\n",
    "\n",
    "# 在实际项目中，为了防止评分相同的时候，元素不是按照上线顺序排序的情况，\n",
    "# 这里可以使用时间戳作为评分，由于时间戳是不停增加的，所以必然先上线的玩家\n",
    "# 时间戳小。对于VIP玩家，你可以在上线时间戳的基础上减去一个数。每次取zpopmin\n",
    "# 这样就能实现先看优先级，再看上线时间的功能了。\n",
    "normal = {\n",
    "    'one': 1,\n",
    "    'two': 1,\n",
    "    'three': 1\n",
    "}\n",
    "\n",
    "client.zadd('enter_game', vip)\n",
    "client.zadd('enter_game', normal)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 109,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(b'kingname', 1000.0)]\n"
     ]
    }
   ],
   "source": [
    "# 先让score最大的玩家进入游戏\n",
    "\n",
    "player = client.zpopmax('enter_game')\n",
    "print(player)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 110,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 110,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 突然来了一个新的VIP\n",
    "client.zadd('enter_game', {'pm': 900})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 111,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(b'pm', 900.0)]\n"
     ]
    }
   ],
   "source": [
    "# 再让当前优先级最高的玩家进入游戏\n",
    "\n",
    "player = client.zpopmax('enter_game')\n",
    "print(player)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 112,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "501.0"
      ]
     },
     "execution_count": 112,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 普通玩家充值成为VIP\n",
    "\n",
    "client.zincrby('enter_game', 500, 'two')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 113,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(b'xiaoming', 800.0)]\n"
     ]
    }
   ],
   "source": [
    "# 由于充值不够，他还是排在另外两个VIP的后面\n",
    "\n",
    "player = client.zpopmax('enter_game')\n",
    "print(player)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 114,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(b'Alice', 800.0)]\n"
     ]
    }
   ],
   "source": [
    "player = client.zpopmax('enter_game')\n",
    "print(player)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 115,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(b'two', 501.0)]\n"
     ]
    }
   ],
   "source": [
    "# 终于轮到他了\n",
    "player = client.zpopmax('enter_game')\n",
    "print(player)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定时任务队列\n",
    "\n",
    "时间戳是一个整数部分10位数的浮点数，有序集合的评分也可以是浮点数，所以如果把Score设置为任务开始时间的时间戳，然后使用`zrangebyscore`每分钟查询一次有序集合，就可以实现精确到分钟的定时任务队列。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 116,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def time_str_2_timestamp(time_str):\n",
    "    time_obj = datetime.datetime.strptime(time_str, '%Y-%m-%d %H:%M:%S')\n",
    "    timestamp = time_obj.timestamp()\n",
    "    return timestamp"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 117,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 创建任务\n",
    "import datetime\n",
    "\n",
    "task_dict = {\n",
    "    'task1': '2019-04-14 15:27:01',\n",
    "    'task2': '2019-04-14 15:27:01',\n",
    "    'task3': '2019-04-14 15:28:01',\n",
    "    'task4': '2019-04-14 15:28:27',\n",
    "    'task5': '2019-04-14 15:29:28',\n",
    "}\n",
    "\n",
    "for key in task_dict:\n",
    "    time_str = task_dict[key]\n",
    "    timestamp = time_str_2_timestamp(time_str)\n",
    "    task_dict[key] = timestamp\n",
    "    client.zadd('timed_task', {key: timestamp})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 119,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "查询起始时间：2019-04-14 15:25:00, 查询截至时间：2019-04-14 15:26:00\n",
      "当前需要执行的任务：[]\n",
      "查询起始时间：2019-04-14 15:26:00, 查询截至时间：2019-04-14 15:27:00\n",
      "当前需要执行的任务：[]\n",
      "查询起始时间：2019-04-14 15:27:00, 查询截至时间：2019-04-14 15:28:00\n",
      "当前需要执行的任务：[(b'task1', 1555226821.0), (b'task2', 1555226821.0)]\n",
      "查询起始时间：2019-04-14 15:28:00, 查询截至时间：2019-04-14 15:29:00\n",
      "当前需要执行的任务：[(b'task3', 1555226881.0), (b'task4', 1555226907.0)]\n",
      "查询起始时间：2019-04-14 15:29:00, 查询截至时间：2019-04-14 15:30:00\n",
      "当前需要执行的任务：[(b'task5', 1555226968.0)]\n"
     ]
    },
    {
     "ename": "KeyboardInterrupt",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mKeyboardInterrupt\u001b[0m                         Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-119-affc314c1cc1>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m     19\u001b[0m                                                withscores=True)\n\u001b[1;32m     20\u001b[0m     \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf'当前需要执行的任务：{task_for_this_minute}'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 21\u001b[0;31m     \u001b[0mtime\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m60\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     22\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     23\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mKeyboardInterrupt\u001b[0m: "
     ]
    }
   ],
   "source": [
    "# 通过一个死循环一不停获取任务\n",
    "import time\n",
    "while True:\n",
    "    # 获取当前时间\n",
    "    now = datetime.datetime.now() \n",
    "    \n",
    "    # 把当前时间的秒和微秒替换为0\n",
    "    now_lower = now.replace(second=0, microsecond=0)\n",
    "    \n",
    "    # 下一分钟\n",
    "    now_higher = now_lower + datetime.timedelta(minutes=1)\n",
    "    \n",
    "    print(f'查询起始时间：{now_lower}, 查询截至时间：{now_higher}')\n",
    "    task_for_this_minute = client.zrangebyscore('timed_task',\n",
    "                                                now_lower.timestamp(),\n",
    "                                                now_higher.timestamp(),\n",
    "                                               0,\n",
    "                                               1000,\n",
    "                                               withscores=True)\n",
    "    print(f'当前需要执行的任务：{task_for_this_minute}')\n",
    "    time.sleep(60)\n",
    "    \n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![读者交流QQ群](https://kingname-1257411235.cos.ap-chengdu.myqcloud.com/2019-02-16-09-59-56.png)\n",
    "![微信公众号](https://kingname-1257411235.cos.ap-chengdu.myqcloud.com/wechatplatform.jpg)\n",
    "![](https://kingname-1257411235.cos.ap-chengdu.myqcloud.com/2019-03-03-20-47-47.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
